use std::path;
use std::fs;

use std::io::ErrorKind;
use std::io::Error as IOError;
use std::io::{Write,Read};
use indicatif::{ProgressBar, ProgressStyle};
use reqwest;
use colored::Colorize;

const SIZE:usize = 51200;
type IoOut<T> = Result<T, IOError>;

pub trait Special {
    fn read_all(&self)-> IoOut<String>;
    fn write_all(&self, content:&str) -> IoOut<()>;
}

pub trait NetStr{
    // fn download(&self) -> IoOut<String>;
    fn download_to(&self, target:&str) -> IoOut<()>;
}
impl <'a>NetStr  for &'a str{
    fn download_to(&self, target:&str) -> IoOut<()>{
        // let tmp_dir = tempfile::Builder::new().prefix("example").tempdir()?;
        // let tempfile::NamedTempFile::new()?;
        if !std::path::Path::new(target).parent().unwrap().exists(){
            let _ = fs::create_dir_all(std::path::Path::new(target).parent().unwrap());
        }
        let mut named_tempfile = fs::File::create(target)?;
        let client = reqwest::Client::new();
        let r = match client.head(*self).send(){
            Ok(r) => r,
            Err(e) => {
                log::error!("{}", e);
                std::process::exit(1);
            }
        };
        if r.status().is_success() {
            if let Some(length_v) = r.headers().get("Content-Length"){
                let len = length_v.to_str().unwrap().parse::<u64>().unwrap();
                let pro = new_pro(len);
                let mut resp = match client.get(*self).send(){
                    Ok(r) => r,
                    Err(e) => {
                        log::error!("{}", e);
                        std::process::exit(1);
                    }
                };
                if resp.status().is_success(){
                    // let mut writer = named_tempfile.reopen();
                    copy_with_progress(&format!("download :{}", self), &mut resp, &mut named_tempfile, &pro)?;
                    // buf.push_str(named_tempfile.path().to_str().unwrap())
                    // return Ok(named_tempfile.path().to_str().unwrap().to_string());
                }
            }else{
                if let Some(length_v) = r.headers().get("content-length"){
                    let len = length_v.to_str().unwrap().parse::<u64>().unwrap();
                    let pro = new_pro(len);
                    let mut resp = match client.get(*self).send(){
                        Ok(r) => r,
                        Err(e) => {
                            log::error!("{},{}", e, self);
                            std::process::exit(1);
                        }
                    };
                    if resp.status().is_success(){
                        // let mut writer = named_tempfile.reopen();
                        copy_with_progress(&format!("download :{}", self), &mut resp, &mut named_tempfile, &pro)?;
                        // buf.push_str(named_tempfile.path().to_str().unwrap())
                        // return Ok(named_tempfile.path().to_str().unwrap().to_string());
                    }
                }else{
                    let mut resp = match client.get(*self).send(){
                        Ok(r) => r,
                        Err(e) => {
                            log::error!("{},{}", e, self);
                            std::process::exit(1);
                        }
                    };
                    let mut buf_bytes: Vec<u8> = Vec::new() ;
                    resp.copy_to(&mut buf_bytes).unwrap();
                    named_tempfile.write_all(&buf_bytes)?;
                    log::info!("download little file :{}", self.green());
                    // buf.push_str(named_tempfile.path().to_str().unwrap());
                }
                
            }
        }    
                
        Ok(())
    }
}
impl <'a>Special  for &'a str{
    fn read_all(&self)-> IoOut<String>{
        let mut buf = String::new();
        let p =  path::Path::new(self);
        if p.exists(){
            let mut reader = fs::File::open(p)?;
            reader.read_to_string(&mut buf)?;
            Ok(buf)
        }else{
            log::error!("File : {}", self.red());
            Err(IOError::new(ErrorKind::NotFound, "not found this file"))
        }
    }

    fn write_all(&self, content:&str) -> IoOut<()>{
        let p =  path::Path::new(self);
        if let Some(pa) = p.parent(){
            if pa.exists(){
                let mut writer = fs::File::create(p)?;
                writer.write_all(content.as_ref())?;
            }else{
                return Err(IOError::new(ErrorKind::NotFound, "not found parrent dir"));
            }
        }else{
            return Err(IOError::new(ErrorKind::NotFound, "not found parrent dir"));
        }
        Ok(())
    }
}


impl Special for String{
    fn write_all(&self, content: &str)-> IoOut<()>{
        self.as_str().write_all(content)
    }
    fn read_all(&self) -> IoOut<String>{
        self.as_str().read_all()
    }
}

impl NetStr for String{

    fn download_to(&self, target:&str) -> IoOut<()>{
        self.as_str().download_to(target)
    }
}

impl Special for path::PathBuf{
    fn write_all(&self, content: &str)-> IoOut<()>{
        self.to_str().unwrap().write_all(content)
    }
    fn read_all(&self) -> IoOut<String>{
        self.to_str().unwrap().read_all()
    }
}

fn new_pro(len:u64)->ProgressBar{
    let pro_bar = ProgressBar::new(len);
    let sty = ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} {bytes_per_sec} ({eta}) {msg}")
        .progress_chars("#>-");
    pro_bar.set_style(sty.clone());
    pro_bar
}


fn copy_with_progress<R: ?Sized, W: ?Sized> (msg:&str, reader: &mut R, writer: &mut W, pro:&ProgressBar,) -> std::io::Result<u64>
where R:Read, W:Write{
    let mut buf = [0;SIZE];
    let mut written = 0;
    loop {
        let len = match reader.read(&mut buf){
            Ok(0) => return Ok(written),
            Ok(len) => len,
            Err(e) => {
                pro.println(&format!("{}", e));
                return Err(e)
            },
        };
        writer.write(&buf[..len]).expect("write error ");
        written += len as u64;
        pro.inc(len as u64);
        pro.set_message(msg);
        buf = [0;SIZE];
    }
}
